/*
 * Copyright (c) 2003, 2004, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package com.sun.jmx.remote.internal;

import java.io.IOException;

import com.sun.jmx.remote.util.ClassLogger;

public abstract class ServerCommunicatorAdmin {

  public ServerCommunicatorAdmin(long timeout) {
    if (logger.traceOn()) {
      logger.trace("Constructor",
          "Creates a new ServerCommunicatorAdmin object " +
              "with the timeout " + timeout);
    }

    this.timeout = timeout;

    timestamp = 0;
    if (timeout < Long.MAX_VALUE) {
      Runnable timeoutTask = new Timeout();
      final Thread t = new Thread(timeoutTask);
      t.setName("JMX server connection timeout " + t.getId());
      // If you change this name you will need to change a unit test
      // (NoServerTimeoutTest)
      t.setDaemon(true);
      t.start();
    }
  }

  /**
   * Tells that a new request message is received.
   * A caller of this method should always call the method
   * <code>rspOutgoing</code> to inform that a response is sent out
   * for the received request.
   *
   * @return the value of the termination flag: <ul><code>true</code> if the connection is already
   * being terminated, <br><code>false</code> otherwise.</ul>
   */
  public boolean reqIncoming() {
    if (logger.traceOn()) {
      logger.trace("reqIncoming", "Receive a new request.");
    }

    synchronized (lock) {
      if (terminated) {
        logger.warning("reqIncoming",
            "The server has decided to close " +
                "this client connection.");
      }
      ++currentJobs;

      return terminated;
    }
  }

  /**
   * Tells that a response is sent out for a received request.
   *
   * @return the value of the termination flag: <ul><code>true</code> if the connection is already
   * being terminated, <br><code>false</code> otherwise.</ul>
   */
  public boolean rspOutgoing() {
    if (logger.traceOn()) {
      logger.trace("reqIncoming", "Finish a request.");
    }

    synchronized (lock) {
      if (--currentJobs == 0) {
        timestamp = System.currentTimeMillis();
        logtime("Admin: Timestamp=", timestamp);
        // tells the adminor to restart waiting with timeout
        lock.notify();
      }
      return terminated;
    }
  }

  /**
   * Called by this class to tell an implementation to do stop.
   */
  protected abstract void doStop();

  /**
   * Terminates this object.
   * Called only by outside, so do not need to call doStop
   */
  public void terminate() {
    if (logger.traceOn()) {
      logger.trace("terminate",
          "terminate the ServerCommunicatorAdmin object.");
    }

    synchronized (lock) {
      if (terminated) {
        return;
      }

      terminated = true;

      // tell Timeout to terminate
      lock.notify();
    }
  }

  // --------------------------------------------------------------
// private classes
// --------------------------------------------------------------
  private class Timeout implements Runnable {

    public void run() {
      boolean stopping = false;

      synchronized (lock) {
        if (timestamp == 0) {
          timestamp = System.currentTimeMillis();
        }
        logtime("Admin: timeout=", timeout);
        logtime("Admin: Timestamp=", timestamp);

        while (!terminated) {
          try {
            // wait until there is no more job
            while (!terminated && currentJobs != 0) {
              if (logger.traceOn()) {
                logger.trace("Timeout-run",
                    "Waiting without timeout.");
              }

              lock.wait();
            }

            if (terminated) {
              return;
            }

            final long remaining =
                timeout - (System.currentTimeMillis() - timestamp);

            logtime("Admin: remaining timeout=", remaining);

            if (remaining > 0) {

              if (logger.traceOn()) {
                logger.trace("Timeout-run",
                    "Waiting with timeout: " +
                        remaining + " ms remaining");
              }

              lock.wait(remaining);
            }

            if (currentJobs > 0) {
              continue;
            }

            final long elapsed =
                System.currentTimeMillis() - timestamp;
            logtime("Admin: elapsed=", elapsed);

            if (!terminated && elapsed > timeout) {
              if (logger.traceOn()) {
                logger.trace("Timeout-run",
                    "timeout elapsed");
              }
              logtime("Admin: timeout elapsed! " +
                  elapsed + ">", timeout);
              // stopping
              terminated = true;

              stopping = true;
              break;
            }
          } catch (InterruptedException ire) {
            logger.warning("Timeout-run", "Unexpected Exception: " +
                ire);
            logger.debug("Timeout-run", ire);
            return;
          }
        }
      }

      if (stopping) {
        if (logger.traceOn()) {
          logger.trace("Timeout-run", "Call the doStop.");
        }

        doStop();
      }
    }
  }

  private void logtime(String desc, long time) {
    timelogger.trace("synchro", desc + time);
  }

  // --------------------------------------------------------------
// private variables
// --------------------------------------------------------------
  private long timestamp;

  private final int[] lock = new int[0];
  private int currentJobs = 0;

  private long timeout;

  // state issue
  private boolean terminated = false;

  private static final ClassLogger logger =
      new ClassLogger("javax.management.remote.misc",
          "ServerCommunicatorAdmin");
  private static final ClassLogger timelogger =
      new ClassLogger("javax.management.remote.timeout",
          "ServerCommunicatorAdmin");
}
